"""
Cross-File Resolution Module.

Advanced import/include resolution and symbol linkage across compilation units.
Handles complex dependency resolution for multi-language projects with support
for various build systems and module systems.
"""

import ast
import json
import re
import os
import sys
from typing import Dict, List, Set, Optional, Tuple, Any, Union
from pathlib import Path
from dataclasses import dataclass, field
from collections import defaultdict, deque
import logging

from .error_handler import AnalysisError, log_info, log_debug, log_warning
from .entry_discovery import EntryPointDiscovery, EntryPoint


@dataclass
class ResolutionConfig:
    """Configuration for cross-file resolution."""
    max_depth: int = 10
    include_external_deps: bool = False
    include_transitive_deps: bool = True
    resolve_symbols: bool = True
    cross_language_resolution: bool = False
    resolution_method: str = "hybrid"  # 'static', 'dynamic', 'hybrid'
    cache_results: bool = True
    languages: List[str] = field(default_factory=lambda: ['python', 'cpp', 'typescript', 'fortran'])
    analyze_circular_deps: bool = False
    find_missing_deps: bool = False
    find_unused_imports: bool = False


@dataclass
class DependencyGraph:
    """Represents a dependency graph."""
    nodes: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    edges: List[Dict[str, Any]] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class DependencyLink:
    """Represents a dependency relationship between files or symbols."""
    source_file: str
    target_file: str
    symbol_name: str
    link_type: str  # 'import', 'include', 'call', 'inheritance', 'reference'
    line_number: int
    confidence: float
    context: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ResolvedSymbol:
    """Represents a resolved symbol with its definition location."""
    name: str
    symbol_type: str  # 'function', 'class', 'variable', 'module', 'namespace'
    definition_file: str
    definition_line: int
    scope: str  # 'local', 'global', 'external', 'builtin'
    language: str
    signature: Optional[str] = None
    documentation: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ModuleInfo:
    """Information about a module or compilation unit."""
    file_path: str
    language: str
    module_name: str
    exports: List[str] = field(default_factory=list)
    imports: List[str] = field(default_factory=list)
    dependencies: List[str] = field(default_factory=list)
    symbols: Dict[str, ResolvedSymbol] = field(default_factory=dict)
    build_info: Dict[str, Any] = field(default_factory=dict)


class CrossFileResolver:
    """Advanced cross-file dependency resolution and symbol linking."""
    
    def __init__(self, project_root: Union[str, Path]):
        """Initialize the cross-file resolver."""
        self.project_root = Path(project_root)
        self.modules: Dict[str, ModuleInfo] = {}
        self.dependency_graph: Dict[str, Set[str]] = defaultdict(set)
        self.reverse_deps: Dict[str, Set[str]] = defaultdict(set)
        self.symbol_index: Dict[str, List[ResolvedSymbol]] = defaultdict(list)
        self.build_cache: Dict[str, Any] = {}
        
        # Language-specific resolvers
        self.resolvers = {
            'python': self._resolve_python_dependencies,
            'cpp': self._resolve_cpp_dependencies,
            'typescript': self._resolve_typescript_dependencies,
            'fortran': self._resolve_fortran_dependencies
        }
        
        # Build system detectors
        self.build_systems = {
            'cmake': self._detect_cmake_project,
            'npm': self._detect_npm_project,
            'python': self._detect_python_project,
            'fortran': self._detect_fortran_project
        }
        
        log_debug(f"Initialized CrossFileResolver for {self.project_root}")
    
    def resolve_project_dependencies(self, 
                                   target_files: List[str] = None,
                                   include_external: bool = False) -> Dict[str, Any]:
        """
        Resolve all dependencies in the project.
        
        Args:
            target_files: Specific files to analyze (None for all)
            include_external: Whether to include external dependencies
            
        Returns:
            Dictionary with resolution results
        """
        log_info("Starting cross-file dependency resolution")
        
        # Step 1: Discover all relevant files
        if target_files:
            files_to_analyze = [Path(f) for f in target_files]
        else:
            files_to_analyze = self._discover_project_files()
        
        # Step 2: Detect build systems
        build_info = self._detect_build_systems()
        
        # Step 3: Analyze each file and build module info
        for file_path in files_to_analyze:
            try:
                module_info = self._analyze_file(file_path, build_info)
                if module_info:
                    self.modules[str(file_path)] = module_info
                    self._update_symbol_index(module_info)
            except Exception as e:
                log_warning(f"Failed to analyze {file_path}: {e}")
                continue
        
        # Step 4: Resolve inter-module dependencies
        self._resolve_inter_module_dependencies()
        
        # Step 5: Build dependency graph
        self._build_dependency_graph()
        
        # Step 6: Resolve symbols across files
        unresolved_symbols = self._resolve_cross_file_symbols()
        
        # Step 7: Detect circular dependencies
        circular_deps = self._detect_circular_dependencies()
        
        resolution_results = {
            'modules': {path: self._module_to_dict(module) 
                       for path, module in self.modules.items()},
            'dependency_graph': {k: list(v) for k, v in self.dependency_graph.items()},
            'symbol_index': {name: [self._symbol_to_dict(sym) for sym in symbols]
                           for name, symbols in self.symbol_index.items()},
            'unresolved_symbols': unresolved_symbols,
            'circular_dependencies': circular_deps,
            'build_info': build_info,
            'statistics': self._generate_statistics()
        }
        
        log_info(f"Resolution complete: {len(self.modules)} modules, "
                f"{len(self.symbol_index)} unique symbols")
        
        return resolution_results
    
    def resolve_symbol(self, symbol_name: str, 
                      context_file: str = None,
                      symbol_type: str = None) -> List[ResolvedSymbol]:
        """
        Resolve a specific symbol to its definition(s).
        
        Args:
            symbol_name: Name of the symbol to resolve
            context_file: File where the symbol is referenced
            symbol_type: Type hint for the symbol
            
        Returns:
            List of possible symbol definitions
        """
        candidates = []
        
        # First, look in symbol index
        if symbol_name in self.symbol_index:
            candidates.extend(self.symbol_index[symbol_name])
        
        # If context file provided, prefer symbols from same module or its dependencies
        if context_file and candidates:
            context_path = str(Path(context_file).resolve())
            
            # Score candidates based on proximity and accessibility
            scored_candidates = []
            for candidate in candidates:
                score = self._calculate_symbol_accessibility_score(
                    candidate, context_path, symbol_type)
                scored_candidates.append((score, candidate))
            
            # Sort by score and return
            scored_candidates.sort(key=lambda x: x[0], reverse=True)
            candidates = [candidate for _, candidate in scored_candidates]
        
        return candidates
    
    def get_file_dependencies(self, file_path: str, 
                            include_transitive: bool = False) -> List[str]:
        """Get dependencies for a specific file."""
        file_path = str(Path(file_path).resolve())
        
        if file_path not in self.dependency_graph:
            return []
        
        if not include_transitive:
            return list(self.dependency_graph[file_path])
        
        # BFS to get all transitive dependencies
        visited = set()
        queue = deque([file_path])
        dependencies = set()
        
        while queue:
            current = queue.popleft()
            if current in visited:
                continue
                
            visited.add(current)
            
            for dep in self.dependency_graph.get(current, []):
                if dep not in visited:
                    dependencies.add(dep)
                    queue.append(dep)
        
        return list(dependencies)
    
    def get_dependents(self, file_path: str, 
                      include_transitive: bool = False) -> List[str]:
        """Get files that depend on the specified file."""
        file_path = str(Path(file_path).resolve())
        
        if file_path not in self.reverse_deps:
            return []
        
        if not include_transitive:
            return list(self.reverse_deps[file_path])
        
        # BFS to get all transitive dependents
        visited = set()
        queue = deque([file_path])
        dependents = set()
        
        while queue:
            current = queue.popleft()
            if current in visited:
                continue
                
            visited.add(current)
            
            for dep in self.reverse_deps.get(current, []):
                if dep not in visited:
                    dependents.add(dep)
                    queue.append(dep)
        
        return list(dependents)
    
    def find_symbol_usages(self, symbol_name: str,
                          definition_file: str = None) -> List[DependencyLink]:
        """Find all usages of a symbol across the project."""
        usages = []
        
        # Get symbol definition
        symbol_candidates = self.resolve_symbol(symbol_name)
        if definition_file:
            symbol_candidates = [s for s in symbol_candidates 
                               if s.definition_file == definition_file]
        
        if not symbol_candidates:
            return usages
        
        target_symbol = symbol_candidates[0]
        
        # Search through all modules for references
        for module_path, module in self.modules.items():
            module_usages = self._find_symbol_in_module(symbol_name, module_path, target_symbol)
            usages.extend(module_usages)
        
        return usages
    
    def _discover_project_files(self) -> List[Path]:
        """Discover all relevant source files in the project."""
        extensions = {'.py', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp',
                     '.ts', '.tsx', '.js', '.jsx', '.f90', '.f95', '.f03', '.f08', '.f'}
        
        files = []
        ignore_dirs = {
            'node_modules', '__pycache__', '.git', '.svn', '.hg',
            'build', 'dist', 'target', 'cmake-build-debug',
            'cmake-build-release', '.pytest_cache', '.tox', 'venv', 'env'
        }
        
        for file_path in self.project_root.rglob('*'):
            if (file_path.is_file() and 
                file_path.suffix.lower() in extensions and
                not any(part in ignore_dirs for part in file_path.parts)):
                files.append(file_path)
        
        return files
    
    def _detect_build_systems(self) -> Dict[str, Any]:
        """Detect build systems and configuration in the project."""
        build_info = {}
        
        for name, detector in self.build_systems.items():
            try:
                info = detector()
                if info:
                    build_info[name] = info
            except Exception as e:
                log_debug(f"Build system detection failed for {name}: {e}")
        
        return build_info
    
    def _detect_cmake_project(self) -> Optional[Dict[str, Any]]:
        """Detect CMake build configuration."""
        cmake_files = list(self.project_root.rglob('CMakeLists.txt'))
        if not cmake_files:
            return None
        
        info = {
            'type': 'cmake',
            'main_file': str(cmake_files[0]),
            'cmake_files': [str(f) for f in cmake_files],
            'include_directories': [],
            'libraries': [],
            'executables': []
        }
        
        # Parse main CMakeLists.txt for basic info
        try:
            with open(cmake_files[0], 'r') as f:
                content = f.read()
                
            # Extract include directories
            include_matches = re.findall(r'include_directories\s*\(\s*([^)]+)\)', content)
            for match in include_matches:
                dirs = [d.strip().strip('"') for d in match.split()]
                info['include_directories'].extend(dirs)
                
            # Extract target executables
            exe_matches = re.findall(r'add_executable\s*\(\s*(\w+)', content)
            info['executables'].extend(exe_matches)
                
        except Exception as e:
            log_debug(f"Failed to parse CMakeLists.txt: {e}")
        
        return info
    
    def _detect_npm_project(self) -> Optional[Dict[str, Any]]:
        """Detect npm/yarn project configuration."""
        package_json = self.project_root / 'package.json'
        if not package_json.exists():
            return None
        
        try:
            with open(package_json, 'r') as f:
                package_data = json.load(f)
            
            info = {
                'type': 'npm',
                'package_file': str(package_json),
                'name': package_data.get('name', ''),
                'main': package_data.get('main', ''),
                'scripts': package_data.get('scripts', {}),
                'dependencies': list(package_data.get('dependencies', {}).keys()),
                'dev_dependencies': list(package_data.get('devDependencies', {}).keys()),
                'workspaces': package_data.get('workspaces', [])
            }
            
            # Check for TypeScript config
            tsconfig = self.project_root / 'tsconfig.json'
            if tsconfig.exists():
                with open(tsconfig, 'r') as f:
                    ts_config = json.load(f)
                info['typescript'] = ts_config
            
            return info
            
        except Exception as e:
            log_debug(f"Failed to parse package.json: {e}")
            return None
    
    def _detect_python_project(self) -> Optional[Dict[str, Any]]:
        """Detect Python project configuration."""
        info = {'type': 'python'}
        
        # Check for setup.py
        setup_py = self.project_root / 'setup.py'
        if setup_py.exists():
            info['setup_file'] = str(setup_py)
            
        # Check for pyproject.toml
        pyproject = self.project_root / 'pyproject.toml'
        if pyproject.exists():
            info['pyproject_file'] = str(pyproject)
            
        # Check for requirements.txt
        requirements = self.project_root / 'requirements.txt'
        if requirements.exists():
            info['requirements_file'] = str(requirements)
            try:
                with open(requirements, 'r') as f:
                    info['requirements'] = [line.strip() for line in f 
                                          if line.strip() and not line.startswith('#')]
            except Exception:
                pass
        
        # Check for virtual environment
        for venv_name in ['venv', 'env', '.venv', '.env']:
            venv_path = self.project_root / venv_name
            if venv_path.exists() and venv_path.is_dir():
                info['virtual_env'] = str(venv_path)
                break
        
        return info if len(info) > 1 else None
    
    def _detect_fortran_project(self) -> Optional[Dict[str, Any]]:
        """Detect Fortran project configuration."""
        fortran_files = list(self.project_root.rglob('*.f90')) + \
                       list(self.project_root.rglob('*.f95')) + \
                       list(self.project_root.rglob('*.f03'))
        
        if not fortran_files:
            return None
        
        info = {
            'type': 'fortran',
            'source_files': [str(f) for f in fortran_files],
            'modules': [],
            'programs': []
        }
        
        # Look for Makefile
        makefile = self.project_root / 'Makefile'
        if makefile.exists():
            info['makefile'] = str(makefile)
        
        return info
    
    def _analyze_file(self, file_path: Path, build_info: Dict[str, Any]) -> Optional[ModuleInfo]:
        """Analyze a single file and extract module information."""
        language = self._detect_language(file_path)
        if not language:
            return None
        
        module_info = ModuleInfo(
            file_path=str(file_path),
            language=language,
            module_name=self._extract_module_name(file_path, language),
            build_info=build_info.get(language, {})
        )
        
        # Use language-specific resolver
        if language in self.resolvers:
            try:
                self.resolvers[language](file_path, module_info)
            except Exception as e:
                log_warning(f"Failed to resolve {language} dependencies in {file_path}: {e}")
        
        return module_info
    
    def _detect_language(self, file_path: Path) -> Optional[str]:
        """Detect programming language from file extension."""
        suffix = file_path.suffix.lower()
        
        language_map = {
            '.py': 'python',
            '.cpp': 'cpp', '.cc': 'cpp', '.cxx': 'cpp',
            '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp',
            '.ts': 'typescript', '.tsx': 'typescript',
            '.js': 'typescript', '.jsx': 'typescript',
            '.f90': 'fortran', '.f95': 'fortran', '.f03': 'fortran',
            '.f08': 'fortran', '.f': 'fortran'
        }
        
        return language_map.get(suffix)
    
    def _extract_module_name(self, file_path: Path, language: str) -> str:
        """Extract module name from file path."""
        if language == 'python':
            # Convert file path to Python module notation
            rel_path = file_path.relative_to(self.project_root)
            if rel_path.name == '__init__.py':
                return str(rel_path.parent).replace(os.sep, '.')
            else:
                return str(rel_path.with_suffix('')).replace(os.sep, '.')
        
        elif language in ['cpp']:
            # Use file stem as module name
            return file_path.stem
        
        elif language == 'typescript':
            # Use relative path without extension
            rel_path = file_path.relative_to(self.project_root)
            return str(rel_path.with_suffix(''))
        
        elif language == 'fortran':
            # Use file stem, but may be overridden by program/module declarations
            return file_path.stem
        
        return file_path.stem
    
    def _resolve_python_dependencies(self, file_path: Path, module_info: ModuleInfo):
        """Resolve Python-specific dependencies."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            tree = ast.parse(content)
            
            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        module_info.imports.append(alias.name)
                        self._resolve_python_module(alias.name, module_info)
                
                elif isinstance(node, ast.ImportFrom):
                    if node.module:
                        module_info.imports.append(node.module)
                        self._resolve_python_module(node.module, module_info)
                
                elif isinstance(node, (ast.FunctionDef, ast.ClassDef)):
                    symbol = ResolvedSymbol(
                        name=node.name,
                        symbol_type='function' if isinstance(node, ast.FunctionDef) else 'class',
                        definition_file=str(file_path),
                        definition_line=node.lineno,
                        scope='local',
                        language='python',
                        signature=self._get_python_signature(node)
                    )
                    module_info.symbols[node.name] = symbol
                    module_info.exports.append(node.name)
        
        except Exception as e:
            log_debug(f"Python AST parsing failed for {file_path}: {e}")
    
    def _resolve_cpp_dependencies(self, file_path: Path, module_info: ModuleInfo):
        """Resolve C++ dependencies."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            # Extract #include statements
            include_pattern = re.compile(r'#include\s*[<"]([^>"]+)[>"]')
            for match in include_pattern.finditer(content):
                include_file = match.group(1)
                module_info.imports.append(include_file)
                self._resolve_cpp_include(include_file, module_info)
            
            # Extract function declarations/definitions
            func_pattern = re.compile(
                r'(?:^\s*(?:extern\s+)?(?:static\s+)?(?:inline\s+)?)?'
                r'(\w+(?:\s*\*|\s*&)?)\s+(\w+)\s*\([^)]*\)\s*(?:[{;])',
                re.MULTILINE
            )
            
            for match in func_pattern.finditer(content):
                return_type, func_name = match.groups()
                if func_name not in ['if', 'while', 'for', 'switch']:  # Avoid keywords
                    line_no = content[:match.start()].count('\n') + 1
                    symbol = ResolvedSymbol(
                        name=func_name,
                        symbol_type='function',
                        definition_file=str(file_path),
                        definition_line=line_no,
                        scope='local',
                        language='cpp',
                        signature=match.group(0).strip()
                    )
                    module_info.symbols[func_name] = symbol
                    module_info.exports.append(func_name)
        
        except Exception as e:
            log_debug(f"C++ parsing failed for {file_path}: {e}")
    
    def _resolve_typescript_dependencies(self, file_path: Path, module_info: ModuleInfo):
        """Resolve TypeScript/JavaScript dependencies."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            # Extract import statements
            import_patterns = [
                re.compile(r'import\s+.*\s+from\s+["\']([^"\']+)["\']'),
                re.compile(r'import\s*\(\s*["\']([^"\']+)["\']\s*\)'),
                re.compile(r'require\s*\(\s*["\']([^"\']+)["\']\s*\)')
            ]
            
            for pattern in import_patterns:
                for match in pattern.finditer(content):
                    import_path = match.group(1)
                    module_info.imports.append(import_path)
                    self._resolve_typescript_module(import_path, module_info)
            
            # Extract function declarations
            func_pattern = re.compile(
                r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)',
                re.MULTILINE
            )
            
            for match in func_pattern.finditer(content):
                func_name = match.group(1)
                line_no = content[:match.start()].count('\n') + 1
                symbol = ResolvedSymbol(
                    name=func_name,
                    symbol_type='function',
                    definition_file=str(file_path),
                    definition_line=line_no,
                    scope='local',
                    language='typescript',
                    signature=match.group(0).strip()
                )
                module_info.symbols[func_name] = symbol
                module_info.exports.append(func_name)
            
            # Extract export statements
            export_pattern = re.compile(r'export\s+(?:default\s+)?(?:const\s+|let\s+|var\s+|function\s+|class\s+)?(\w+)')
            for match in export_pattern.finditer(content):
                export_name = match.group(1)
                if export_name not in module_info.exports:
                    module_info.exports.append(export_name)
        
        except Exception as e:
            log_debug(f"TypeScript parsing failed for {file_path}: {e}")
    
    def _resolve_fortran_dependencies(self, file_path: Path, module_info: ModuleInfo):
        """Resolve Fortran dependencies."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            # Extract use statements
            use_pattern = re.compile(r'^\s*use\s+(\w+)', re.MULTILINE | re.IGNORECASE)
            for match in use_pattern.finditer(content):
                module_name = match.group(1)
                module_info.imports.append(module_name)
                self._resolve_fortran_module(module_name, module_info)
            
            # Extract module declarations
            module_pattern = re.compile(r'^\s*module\s+(\w+)', re.MULTILINE | re.IGNORECASE)
            for match in module_pattern.finditer(content):
                mod_name = match.group(1)
                module_info.module_name = mod_name
                module_info.exports.append(mod_name)
            
            # Extract subroutine/function declarations
            subprogram_pattern = re.compile(
                r'^\s*(?:subroutine|function)\s+(\w+)', 
                re.MULTILINE | re.IGNORECASE
            )
            
            for match in subprogram_pattern.finditer(content):
                sub_name = match.group(1)
                line_no = content[:match.start()].count('\n') + 1
                symbol = ResolvedSymbol(
                    name=sub_name,
                    symbol_type='function',
                    definition_file=str(file_path),
                    definition_line=line_no,
                    scope='local',
                    language='fortran',
                    signature=match.group(0).strip()
                )
                module_info.symbols[sub_name] = symbol
                module_info.exports.append(sub_name)
        
        except Exception as e:
            log_debug(f"Fortran parsing failed for {file_path}: {e}")
    
    def _resolve_python_module(self, module_name: str, current_module: ModuleInfo):
        """Resolve Python module import to file path."""
        # Try to find the module file
        module_parts = module_name.split('.')
        
        # Search in project directory
        search_paths = [self.project_root]
        
        # Add sys.path for standard library detection
        for sys_path in sys.path:
            if Path(sys_path).exists():
                search_paths.append(Path(sys_path))
        
        for search_path in search_paths:
            # Try as package
            package_path = search_path
            for part in module_parts:
                package_path = package_path / part
            
            if (package_path / '__init__.py').exists():
                current_module.dependencies.append(str(package_path / '__init__.py'))
                return
            
            # Try as module file
            module_file = package_path.with_suffix('.py')
            if module_file.exists():
                current_module.dependencies.append(str(module_file))
                return
        
        # Mark as external dependency
        current_module.dependencies.append(f"external:{module_name}")
    
    def _resolve_cpp_include(self, include_file: str, current_module: ModuleInfo):
        """Resolve C++ include to file path."""
        # Check if it's a system header (angle brackets)
        if include_file.startswith('/') or not include_file.replace('.', '').replace('_', '').isalnum():
            current_module.dependencies.append(f"system:{include_file}")
            return
        
        # Search for local includes
        search_paths = [
            self.project_root,
            current_module.file_path.parent if hasattr(current_module, 'file_path') else self.project_root
        ]
        
        # Add CMake include directories if available
        cmake_info = current_module.build_info.get('cmake', {})
        for include_dir in cmake_info.get('include_directories', []):
            search_paths.append(self.project_root / include_dir)
        
        for search_path in search_paths:
            include_path = Path(search_path) / include_file
            if include_path.exists():
                current_module.dependencies.append(str(include_path))
                return
        
        # Mark as external
        current_module.dependencies.append(f"external:{include_file}")
    
    def _resolve_typescript_module(self, import_path: str, current_module: ModuleInfo):
        """Resolve TypeScript module import."""
        # Handle relative imports
        if import_path.startswith('.'):
            base_path = Path(current_module.file_path).parent
            resolved_path = (base_path / import_path).resolve()
            
            # Try different extensions
            for ext in ['.ts', '.tsx', '.js', '.jsx']:
                if resolved_path.with_suffix(ext).exists():
                    current_module.dependencies.append(str(resolved_path.with_suffix(ext)))
                    return
            
            # Try as directory with index file
            for ext in ['.ts', '.tsx', '.js', '.jsx']:
                index_file = resolved_path / f'index{ext}'
                if index_file.exists():
                    current_module.dependencies.append(str(index_file))
                    return
        
        # Handle node_modules imports
        elif not import_path.startswith('.'):
            node_modules = self.project_root / 'node_modules' / import_path
            if node_modules.exists():
                # Look for package.json main field
                package_json = node_modules / 'package.json'
                if package_json.exists():
                    try:
                        with open(package_json, 'r') as f:
                            pkg_data = json.load(f)
                        main_file = pkg_data.get('main', 'index.js')
                        main_path = node_modules / main_file
                        if main_path.exists():
                            current_module.dependencies.append(str(main_path))
                            return
                    except Exception:
                        pass
            
            # Mark as external npm package
            current_module.dependencies.append(f"npm:{import_path}")
    
    def _resolve_fortran_module(self, module_name: str, current_module: ModuleInfo):
        """Resolve Fortran module dependency."""
        # Search for .mod files or source files with module declaration
        for file_path in self.project_root.rglob('*.f90'):
            try:
                with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                    content = f.read()
                
                module_pattern = re.compile(rf'^\s*module\s+{module_name}\b', 
                                          re.MULTILINE | re.IGNORECASE)
                if module_pattern.search(content):
                    current_module.dependencies.append(str(file_path))
                    return
            except Exception:
                continue
        
        # Mark as external
        current_module.dependencies.append(f"external:{module_name}")
    
    def _update_symbol_index(self, module_info: ModuleInfo):
        """Update the global symbol index with symbols from a module."""
        for symbol_name, symbol in module_info.symbols.items():
            self.symbol_index[symbol_name].append(symbol)
    
    def _resolve_inter_module_dependencies(self):
        """Resolve dependencies between analyzed modules."""
        for module_path, module in self.modules.items():
            resolved_deps = []
            
            for dep in module.dependencies:
                if dep.startswith(('external:', 'system:', 'npm:')):
                    # Keep external dependencies as-is
                    resolved_deps.append(dep)
                else:
                    # Resolve to actual module if it exists
                    dep_path = str(Path(dep).resolve())
                    if dep_path in self.modules:
                        resolved_deps.append(dep_path)
                    else:
                        resolved_deps.append(dep)
            
            module.dependencies = resolved_deps
    
    def _build_dependency_graph(self):
        """Build the dependency graph from module information."""
        for module_path, module in self.modules.items():
            for dep in module.dependencies:
                if not dep.startswith(('external:', 'system:', 'npm:')):
                    self.dependency_graph[module_path].add(dep)
                    self.reverse_deps[dep].add(module_path)
    
    def _resolve_cross_file_symbols(self) -> List[str]:
        """Resolve symbols that are referenced but not defined locally."""
        unresolved = []
        
        # This would need more sophisticated analysis
        # For now, just return empty list as placeholder
        return unresolved
    
    def _detect_circular_dependencies(self) -> List[List[str]]:
        """Detect circular dependencies using DFS."""
        def dfs(node, path, visited, rec_stack):
            visited.add(node)
            rec_stack.add(node)
            path.append(node)
            
            for neighbor in self.dependency_graph.get(node, []):
                if neighbor.startswith(('external:', 'system:', 'npm:')):
                    continue
                    
                if neighbor not in visited:
                    cycle = dfs(neighbor, path[:], visited, rec_stack)
                    if cycle:
                        return cycle
                elif neighbor in rec_stack:
                    # Found a cycle
                    cycle_start = path.index(neighbor)
                    return path[cycle_start:] + [neighbor]
            
            rec_stack.discard(node)
            return None
        
        visited = set()
        cycles = []
        
        for node in self.dependency_graph:
            if node not in visited:
                cycle = dfs(node, [], visited, set())
                if cycle:
                    cycles.append(cycle)
        
        return cycles
    
    def _calculate_symbol_accessibility_score(self, symbol: ResolvedSymbol,
                                            context_file: str, 
                                            symbol_type: str = None) -> float:
        """Calculate how accessible a symbol is from a given context."""
        score = 0.0
        
        # Same file gets highest score
        if symbol.definition_file == context_file:
            score += 100.0
        
        # Same directory gets bonus
        elif (Path(symbol.definition_file).parent == 
              Path(context_file).parent):
            score += 50.0
        
        # Dependency relationship
        if context_file in self.dependency_graph:
            if symbol.definition_file in self.dependency_graph[context_file]:
                score += 30.0
        
        # Symbol type match
        if symbol_type and symbol.symbol_type == symbol_type:
            score += 20.0
        
        # Scope preferences
        scope_scores = {'local': 10.0, 'global': 5.0, 'external': 1.0, 'builtin': 0.5}
        score += scope_scores.get(symbol.scope, 0.0)
        
        return score
    
    def _find_symbol_in_module(self, symbol_name: str, 
                             module_path: str, 
                             target_symbol: ResolvedSymbol) -> List[DependencyLink]:
        """Find usages of a symbol within a specific module."""
        # This would need language-specific parsing to find actual usages
        # For now, return empty list as placeholder
        return []
    
    def _get_python_signature(self, node: ast.FunctionDef) -> str:
        """Extract function signature from Python AST node."""
        args = []
        for arg in node.args.args:
            args.append(arg.arg)
        return f"def {node.name}({', '.join(args)})"
    
    def _module_to_dict(self, module: ModuleInfo) -> Dict[str, Any]:
        """Convert ModuleInfo to dictionary."""
        return {
            'file_path': module.file_path,
            'language': module.language,
            'module_name': module.module_name,
            'exports': module.exports,
            'imports': module.imports,
            'dependencies': module.dependencies,
            'symbols': {name: self._symbol_to_dict(symbol) 
                       for name, symbol in module.symbols.items()},
            'build_info': module.build_info
        }
    
    def _symbol_to_dict(self, symbol: ResolvedSymbol) -> Dict[str, Any]:
        """Convert ResolvedSymbol to dictionary."""
        return {
            'name': symbol.name,
            'symbol_type': symbol.symbol_type,
            'definition_file': symbol.definition_file,
            'definition_line': symbol.definition_line,
            'scope': symbol.scope,
            'language': symbol.language,
            'signature': symbol.signature,
            'documentation': symbol.documentation,
            'metadata': symbol.metadata
        }
    
    def _generate_statistics(self) -> Dict[str, Any]:
        """Generate resolution statistics."""
        stats = {
            'total_modules': len(self.modules),
            'total_symbols': sum(len(module.symbols) for module in self.modules.values()),
            'languages': defaultdict(int),
            'dependency_types': defaultdict(int),
            'symbol_types': defaultdict(int)
        }
        
        for module in self.modules.values():
            stats['languages'][module.language] += 1
            
            for symbol in module.symbols.values():
                stats['symbol_types'][symbol.symbol_type] += 1
            
            for dep in module.dependencies:
                if dep.startswith('external:'):
                    stats['dependency_types']['external'] += 1
                elif dep.startswith('system:'):
                    stats['dependency_types']['system'] += 1
                elif dep.startswith('npm:'):
                    stats['dependency_types']['npm'] += 1
                else:
                    stats['dependency_types']['local'] += 1
        
        return dict(stats)


# API Compatibility Exports - for test compatibility
from enum import Enum

class ResolutionType(Enum):
    """Enumeration of resolution types."""
    IMPORT = "import"
    INCLUDE = "include"
    MODULE = "module"
    NAMESPACE = "namespace"
    SYMBOL = "symbol"
    LOCAL = "local"
    EXTERNAL = "external"
    UNKNOWN = "unknown"

class DependencyType(Enum):
    """Enumeration of dependency types."""
    IMPORT = "import"
    INCLUDE = "include"
    REQUIRE = "require"
    USE = "use"
    MODULE = "module"
    LIBRARY = "library"
    SYSTEM = "system"
    EXTERNAL = "external"
    LOCAL = "local"

@dataclass
class ResolutionResult:
    """Result of dependency resolution."""
    resolved_path: Optional[str]
    resolution_type: ResolutionType
    confidence: float
    error: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

@dataclass  
class DependencyNode:
    """Node in the dependency graph."""
    name: str
    path: Optional[str]
    dependencies: List[str] = field(default_factory=list)
    dependents: List[str] = field(default_factory=list)
    resolution_type: Optional[ResolutionType] = None